<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------

namespace buwang\service;

/**
 * 菜单节点自动扫描
 * Class NodeService
 * @package buwang\service
 */
class NodeService
{
    public static function getMethods($force = false)
    {
        static $data = [];
        /*if (empty($force)) {
            if (count($data) > 0) return $data;
            $data = $this->app->cache->get('system_auth_node', []);
            if (count($data) > 0) return $data;
        } else {
            $data = [];
        }*/

        //不生成菜单的方法名 通过获取基类的方法
        $ignores = get_class_methods('\buwang\base\BaseController');

        foreach (self::scanDirectory(base_path()) as $file) {
            if (preg_match("|/(\w+)/(\w+)/controller/(.+)\.php$|i", $file, $matches)) {
                list(, $namespace, $appname, $classname) = $matches;

                //通过反射获取当前php文件的实例
                $class = new \ReflectionClass(strtr("{$namespace}/{$appname}/controller/{$classname}", '/', '\\'));

                //获取控制器的访问路径 控制器目录由.拼接,控制器名称由驼峰转为_
                $prefix = strtr("{$appname}/" . self::nameTolower($classname), '\\', '/');

                $data[$prefix] = self::parseComment($class->getDocComment(), $classname);
                foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
                    $method_name = $method->getName();
                    if (in_array($method_name, $ignores)) continue;

                    $data["{$prefix}/{$method_name}"] = self::parseComment($method->getDocComment(), $method_name);
                }
            }
        }

        $data = array_change_key_case($data, CASE_LOWER);
//        $this->app->cache->set('system_auth_node', $data);
        return $data;
    }

    /**
     * 扫描目录,获取指定后缀的文件列表数组
     * @param string $path 要扫描的目录
     * @param array $data 文件列表默认数组
     * @param string $ext 扫描文件后缀,默认为 php
     * @return array
     */
    private static function scanDirectory(string $path, array $data = [], string $ext = 'php')
    {
        foreach (glob("{$path}*") as $item) {
            if (is_dir($item)) {
                $data = array_merge($data, self::scanDirectory("{$item}/"));
            } elseif (is_file($item) && pathinfo($item, PATHINFO_EXTENSION) === $ext) {
                $data[] = strtr($item, '\\', '/');
            }
        }
        return $data;
    }

    /**
     * 驼峰转下划线规则
     * @param string $name
     * @return string
     */
    public static function nameTolower($name)
    {
        $dots = [];
        foreach (explode('.', strtr($name, '/', '.')) as $dot) {
            $dots[] = trim(preg_replace("/[A-Z]/", "_\\0", $dot), "_");
        }
        return strtolower(join('.', $dots));
    }

    /**
     * 解析硬节点属性
     * @param string $comment
     * @param string $default
     * @return array
     */
    private static function parseComment(string $comment, string $default = '')
    {
        /**
         * 操作的名称
         * @auth true  # 表示需要验证权限
         * @menu true  # 在菜单编辑的节点可选项
         * @login true # 需要强制登录可访问
         */
        $text = strtr($comment, "\n", ' ');

        $title = preg_replace('/^\/\*\s*\*\s*\*\s*(.*?)\s*\*.*?$/', '$1', $text);
        foreach (['@auth', '@menu', '@login'] as $find) if (stripos($title, $find) === 0) $title = $default;

        return [
            'title' => $title ? $title : $default,
            'isauth' => intval(preg_match('/@auth\s*true/i', $text)),
            'ismenu' => intval(preg_match('/@menu\s*true/i', $text)),
            'islogin' => intval(preg_match('/@login\s*true/i', $text)),
        ];
    }
}